package cn.wch.ch934xlib.chip;

import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;

import androidx.annotation.NonNull;

import java.util.Arrays;
import java.util.Locale;

import cn.wch.ch934xlib.assist.GPIOAssist;
import cn.wch.ch934xlib.assist.ModeAssist;
import cn.wch.ch934xlib.exception.UartLibException;
import cn.wch.ch934xlib.gpio.GPIO_DIR;
import cn.wch.ch934xlib.gpio.GPIO_VALUE;
import cn.wch.ch934xlib.utils.LogUtil;


public class ChipParameterUtil {
    private static final byte VENDOR_WRITE_REG_TYPE= (byte) 0xC0;
    private static final byte VENDOR_WRITE_BIT_TYPE= (byte) 0x80;
    private static final byte VENDOR_SET_TYPE= (byte) 0x20;
    private static final byte REG_MCR=0x04;


    public static final byte PORT_OFFSET=0x04;
    public static final byte MODE_NORMAL=0x00;

    public static final byte MODE_HARDFLOW=0x03;
    private static final byte VENDOR_WRITE_BIT_EXT_TYPE= (byte) 0x90;

    public static final byte REG_IIR_THR=0x02;
    //GPIO
    public static final byte MAXGPIO= 12;

    public static final byte MODE_GPIO=0x02;

    private static final byte GPIO_HIGH =1;
    private static final byte GPIO_LOW =0;

    private static final byte GPIO_DIR_OUT=  0x01;
    private static final byte GPIO_DIR_IN=  0x00;

    public static final byte REG_UART_MODE= (byte) 0x97;
    public static final byte REG_GPIO_DIR= (byte) 0x98;
    public static final byte REG_GPIO_OUT= (byte) 0x99;
    public static final byte REG_GPIO_IN= (byte) 0x9b;


    public static boolean setGeneralBaudRate(@NonNull UsbDevice device, @NonNull UsbDeviceConnection connection, int serialNumber, int baudRate,
                                             int dataBit, int stop, int parity, boolean flow, @NonNull ChipEntity chipEntity,int serialCount){
        byte pendent=0x00;
        int clockRate=1843200;
        byte databit=0,paritybit=0,stopbit=0;
        byte[] baud=null;
        int baud1,baud2,baud3=0;
        LogUtil.d("set baudRate");
        if(baudRate==0){
            baudRate=9600;
        }
        if(baudRate>115200){
            pendent=0x01;
            clockRate=44236800;
        }

        baud=FastSetBaudRate(clockRate,baudRate);
        if(baud==null){
            return false;
        }

        baud1=baud[0];
        baud2=baud[1];
        switch (baudRate){
            case 250000:
                baud3=1;
                break;
            case 500000:
                baud3=2;
                break;
            case 1000000:
                baud3=3;
                break;
            case 1500000:
                baud3=4;
                break;
            case 3000000:
                baud3=5;
                break;
            case 12000000:
                baud3=6;
                break;
            default:
                baud3=0;
                break;
        }
        if(stop==2){
            stopbit=0x04;
        }

        switch (parity) {
            case 1: /* ODD */
                paritybit =(byte) (0x08);
                break;
            case 2: /* Even */
                paritybit =(byte) (0x01<<4+0x08);
                break;
            case 3: /* Mark */
                paritybit =(byte) (0x02<<4+0x08);
                break;
            case 4: /* Space */
                paritybit =(byte) (0x03<<4+0x08);
                break;
            default: /* None */
                paritybit = 0x00;
                break;
        }

        switch (dataBit) {
            case 5:
                databit= 0x00;
                break;
            case 6:
                databit= 0x01;
                break;
            case 7:
                databit= 0x02;
                break;
            case 8:
            default:
                databit= 0x03;
                break;
        }

        int request= VENDOR_WRITE_BIT_TYPE;
        int regaddr=0x10*(serialNumber)+0x08;
        int addr=regaddr+0x01;
        int value=pendent+0x50;
        int ret=ChipTransferUtil.cmdOut(connection,chipEntity.getCtrlEpOut(),request,addr,value);
        if (ret < 0)
            return false;
        byte[] buffer=new byte[6];
        Arrays.fill(buffer,(byte) 0x00);
        buffer[0]=VENDOR_SET_TYPE;
        buffer[1]= (byte) (regaddr+0x03);
        buffer[2]= (byte) baud1;
        buffer[3]= (byte) baud2;
        buffer[4]= (byte) baud3;

        ret=ChipTransferUtil.cmdOut(connection,chipEntity.getCtrlEpOut(),buffer);
        if (ret < 0)
            return false;
        byte[] buf=new byte[3];
        buf[0]=VENDOR_WRITE_REG_TYPE;
        buf[1]= (byte) (regaddr+0x03);
        buf[2]= (byte) (databit|paritybit|stopbit);
        ret=ChipTransferUtil.cmdOut(connection,chipEntity.getCtrlEpOut(),buf);
        if (ret < 0){
            return false;
        }
        //flow
        if(!setFlowEnabled(device,connection,chipEntity.getCtrlEpOut(),serialNumber,flow,serialCount)){
            LogUtil.d("set flow fail");
            return false;
        }
        //record flow
        try {
            ModeAssist.getInstance().setFlow(device,serialNumber,flow);
        } catch (UartLibException e) {
            LogUtil.d(e.getMessage());
            return false;
        }
        return true;
    }

    private static byte[] FastSetBaudRate(int clockRate,int baudRate){
        byte[] res=new byte[2];
        int division=0,x2=0;
        if(baudRate<0 || baudRate>12000000){
            return null;
        }
        //Caculate division From baudRate
        if(baudRate==2000000){
            res[0]=2;
            res[1]=0;
        }else {
            division = 10 * clockRate / 16 / baudRate;
            x2 = division % 10;
            division /= 10;
            if( x2 >= 5 ){
                division++;
            }

            res[0]=(byte)division;
            res[1]=(byte)(division>>8);
        }
        return res;
    }

//    public static boolean setUARTMode(UsbDevice usbDevice,UsbDeviceConnection connection, UsbEndpoint mCtrlPointOut,int serialNumber, boolean enableFlow,int serialCount){
//
//        byte[] buffer=new byte[3];
//        int ret=0;
//        byte request=0;
//        byte regaddr=0;
//        byte mode4=0;
//        LogUtil.d("set flow");
//        if(enableFlow){
//            regaddr= (byte) (0x10*serialNumber+0x08);
//            buffer[0]=VENDOR_WRITE_BIT_TYPE;
//            buffer[1]= (byte) (regaddr+REG_MCR);
//            buffer[2]=0x51;
//            ret = ChipTransferUtil.cmdOut(connection, mCtrlPointOut, buffer);
//            if(ret<0){
//                return false;
//            }
//        }else {
//            buffer[0]=VENDOR_WRITE_BIT_TYPE;
//            buffer[1]= (byte) (regaddr+REG_MCR);
//            buffer[2]=0x50;
//            ret = ChipTransferUtil.cmdOut(connection, mCtrlPointOut, buffer);
//            if(ret<0){
//                return false;
//            }
//        }
//        for (int i = 0; i < serialCount; i++) {
//            if(i!=serialNumber){
//                try {
//                    boolean b = ModeAssist.getInstance().queryFlow(usbDevice,i);
//                    mode4|=(b? MODE_HARDFLOW:MODE_NORMAL)<<(i*2);
//                } catch (UartLibException e) {
//                    LogUtil.d(e.getMessage());
//                    return false;
//                }
//
//            }
//        }
//        mode4|=(enableFlow?MODE_HARDFLOW:MODE_NORMAL)<<(serialNumber*2);
//        request= (byte) (VENDOR_WRITE_BIT_EXT_TYPE+serialNumber+PORT_OFFSET);
//        regaddr=REG_UART_MODE;
//        byte[] buf=new byte[4];
//        Arrays.fill(buf,(byte)0x00);
//        buf[0]=request;
//        buf[1]=regaddr;
//        buf[2]=mode4;
//        int i = ChipTransferUtil.cmdOut(connection, mCtrlPointOut, buf);
//        return i>=0;
//    }

    public static boolean setFlowEnabled(UsbDevice usbDevice, UsbDeviceConnection connection, UsbEndpoint mCtrlPointOut, int serialNumber, boolean enableFlow, int serialCount){
        return setUARTMode(usbDevice, connection, mCtrlPointOut, 0, (byte) serialNumber,enableFlow?Mode.HARDFLOW:Mode.NORMAL, serialCount);
    }

    public static boolean setGPIOEnabled(UsbDevice usbDevice,UsbDeviceConnection connection, UsbEndpoint mCtrlPointOut,int serialNumber,boolean enableGPIO,int serialCount) throws UartLibException {
        Mode mode = enableGPIO ? Mode.GPIO : Mode.NORMAL;

        Mode currentMode = ModeAssist.getInstance().queryMode(usbDevice, serialNumber);
        if(serialNumber>=serialCount){
            throw new UartLibException("serialNumber is invalid");
        }
        if(currentMode==Mode.HARDFLOW){
            throw new UartLibException("serial "+serialNumber+"is in HARDFLOW mode");
        }

        boolean ret = setUARTMode(usbDevice, connection, mCtrlPointOut, 0, (byte) serialNumber, mode, serialCount);
        if(ret){
            ModeAssist.getInstance().setMode(usbDevice,serialNumber,mode);


            if(mode==Mode.NORMAL){
                //restores gpio related vars here
                for (int i = serialNumber * 3; i < serialNumber * 3 + 3; i++) {
                    //??
                    GPIOAssist.getInstance().setGPIODir(usbDevice,serialNumber,i,GPIO_DIR.IN);
                    GPIOAssist.getInstance().setGPIOValue(usbDevice,serialNumber,i,GPIO_VALUE.LOW);

                }
            }
        }
        return ret;
    }

    public static boolean setGPIODir(UsbDevice usbDevice, UsbDeviceConnection connection, UsbEndpoint mCtrlPointOut, int gpioNumber, @NonNull GPIO_DIR dir, int gpioCount) throws UartLibException {
        int gpiogroup = gpioNumber / 3;

        int serialNumber=gpiogroup;
        Mode mode = ModeAssist.getInstance().queryMode(usbDevice, serialNumber);

        if(gpioNumber>=gpioCount){
            throw new UartLibException("gpioNumber is invalid");
        }

        if(mode!=Mode.GPIO){

            throw new UartLibException("serial "+serialNumber+" is not in GPIO mode");
        }
        boolean ret = setGPIODirReal(usbDevice, connection, mCtrlPointOut, 0,serialNumber, gpioNumber, dir.getValue(), gpioCount);
        if(ret){
            //save info
            GPIOAssist.getInstance().setGPIODir(usbDevice,serialNumber,gpioNumber,dir);
            if(dir==GPIO_DIR.IN){
                GPIOAssist.getInstance().setGPIOValue(usbDevice,serialNumber,gpioNumber,GPIO_VALUE.HIGH);
            }
        }
        return ret;

    }

    public static boolean setGPIOValue(UsbDevice usbDevice, UsbDeviceConnection connection, UsbEndpoint mCtrlPointOut, int gpioNumber, @NonNull GPIO_VALUE value, int gpioCount) throws UartLibException {
        int gpiogroup = gpioNumber / 3;

        int serialNumber=gpiogroup;

        Mode mode = ModeAssist.getInstance().queryMode(usbDevice, serialNumber);;

        GPIO_DIR gpio_dir = GPIOAssist.getInstance().queryGPIODir(usbDevice, serialNumber, gpioNumber);

        if(gpioNumber>=gpioCount){
            throw new UartLibException("gpioNumber is invalid");
        }

        /*if(mode!=Mode.GPIO){
            throw new UartLibException("serial "+serialNumber+" is not in GPIO mode");
        }*/

        if(gpio_dir!=GPIO_DIR.OUT){
            throw new UartLibException(String.format(Locale.getDefault(),"the direction of GPIO %d (serial %d) is not OUT",gpioNumber,serialNumber));
        }
        boolean ret = setGPIOValueReal(usbDevice, connection, mCtrlPointOut, 0,serialNumber, gpioNumber, value.getValue(), gpioCount);
        if(ret){
            //save info
            GPIOAssist.getInstance().setGPIOValue(usbDevice,serialNumber,gpioNumber,value);

        }
        return ret;

    }

    public static boolean getGPIOValue(UsbDevice usbDevice,UsbDeviceConnection connection, UsbEndpoint mCtrlPointOut,int gpioCount,long timeout) throws UartLibException {

        Short valueReal = getGPIOValueReal(usbDevice, connection, mCtrlPointOut, 0, gpioCount, timeout);
        if(valueReal!=null){
            for (int i = 0; i < gpioCount; i++) {
                LogUtil.d("GPIO VALUE-->"+valueReal);
                int gpiogroup = i / 3;
                GPIO_VALUE gpio_value = (valueReal & (0x0001 << i)) != 0 ? GPIO_VALUE.HIGH : GPIO_VALUE.LOW;

                GPIOAssist.getInstance().setGPIOValue(usbDevice,gpiogroup,i,gpio_value);
            }
            return true;
        }else {
            LogUtil.d("get GPIO value fail!");
            return false;
        }
    }

    public static GPIO_VALUE queryGPIOValueFromCache(UsbDevice usbDevice,int gpioNumber,int gpioCount) throws UartLibException {
        int gpiogroup = gpioNumber / 3;

        int serialNumber=gpiogroup;

        Mode mode = ModeAssist.getInstance().queryMode(usbDevice, serialNumber);;

        GPIO_DIR gpio_dir = GPIOAssist.getInstance().queryGPIODir(usbDevice, serialNumber, gpioNumber);

        if(gpioNumber>=gpioCount){
            throw new UartLibException("gpioNumber is invalid");
        }

        /*if(mode!=Mode.GPIO){
            throw new UartLibException("serial "+serialNumber+" is not in GPIO mode");
        }*/

        if(gpio_dir!=GPIO_DIR.IN){
            throw new UartLibException(String.format(Locale.getDefault(),"the direction of GPIO %d (serial %d) is not IN",gpioNumber,serialNumber));
        }

        return GPIOAssist.getInstance().queryGPIOValue(usbDevice,serialNumber,gpioNumber);
    }





    //portnum 第几个串口操作
    //index 第几组GPIO
    private static boolean setUARTMode(UsbDevice usbDevice,UsbDeviceConnection connection, UsbEndpoint mCtrlPointOut,int portnum
            , byte index,@NonNull Mode mode,int serialCount){

        byte[] buffer=new byte[3];
        int ret=0;
        byte request=0;
        byte regaddr=0;
        byte mode4=0;
        LogUtil.d("set UARTMode");
        Mode current=null;
        try {
            current = ModeAssist.getInstance().queryMode(usbDevice, portnum);
        } catch (UartLibException e) {
            LogUtil.d(e.getMessage());
            return false;
        }
        if(mode==Mode.HARDFLOW){
            regaddr= (byte) (0x10*index+0x08);
            buffer[0]=VENDOR_WRITE_BIT_TYPE;
            buffer[1]= (byte) (regaddr+REG_MCR);
            buffer[2]=0x51;
            ret = ChipTransferUtil.cmdOut(connection, mCtrlPointOut, buffer);
            if(ret<0){
                return false;
            }
        }
        if(mode==Mode.NORMAL){
            buffer[0]=VENDOR_WRITE_BIT_TYPE;
            buffer[1]= (byte) (regaddr+REG_MCR);
            buffer[2]=0x50;
            ret = ChipTransferUtil.cmdOut(connection, mCtrlPointOut, buffer);
            if(ret<0){
                return false;
            }
        }

        for (int i = 0; i < serialCount; i++) {
            if(i!=index){
                try {
                    Mode mode1 = ModeAssist.getInstance().queryMode(usbDevice, i);
                    mode4|=(mode1.getValue())<<(i*2);
                } catch (UartLibException e) {
                    e.printStackTrace();
                    return false;
                }


            }
        }
        mode4|=(mode.getValue())<<(index*2);
        request= (byte) (VENDOR_WRITE_BIT_EXT_TYPE+portnum+PORT_OFFSET);
        regaddr=REG_UART_MODE;
        byte[] buf=new byte[4];
        Arrays.fill(buf,(byte)0x00);
        buf[0]=request;
        buf[1]=regaddr;
        buf[2]=mode4;
        int i = ChipTransferUtil.cmdOut(connection, mCtrlPointOut, buf);
        return i>=0;
    }

    private static boolean setGPIODirReal(UsbDevice usbDevice, UsbDeviceConnection connection, UsbEndpoint mCtrlPointOut
            , int portnum,int serialNumber, int gpioNumber, byte gpioDir, int GPIOCount){
        byte[] buffer=new byte[4];
        int ret=0;
        byte request=0;
        byte regaddr=0;
        short gpiodirs = 0;
        int i;


        for (i = 0; i < GPIOCount; i++) {
            if (i != gpioNumber) {
                try {
                    int tempSerialNumber=i/3;
                    GPIO_DIR dir = GPIOAssist.getInstance().queryGPIODir(usbDevice,tempSerialNumber , i);
                    gpiodirs |= (dir.getValue()) << i;
                } catch (UartLibException e) {
                    LogUtil.d(e.getMessage());
                    return false;
                }
            }
        }
        gpiodirs |= gpioDir << gpioNumber;
        request = (byte) (VENDOR_WRITE_BIT_EXT_TYPE + portnum + PORT_OFFSET);
        regaddr = REG_GPIO_DIR;
        buffer[0] = request;
        buffer[1] = regaddr;
        buffer[2] = (byte) gpiodirs;
        buffer[3] = (byte) (gpiodirs >> 8);
        return ChipTransferUtil.cmdOut(connection,mCtrlPointOut,buffer)>=0;
    }


    private static boolean setGPIOValueReal(UsbDevice usbDevice, UsbDeviceConnection connection, UsbEndpoint mCtrlPointOut
            ,int portnum, int serialNumber, int gpioNumber, byte gpioValue, int GPIOCount){
        byte[] buffer=new byte[4];
        int ret=0;
        byte request=0;
        byte regaddr=0;
        short gpiovalues = 0;
        int i;

        for (i = 0; i < GPIOCount; i++) {
            if (i != gpioNumber) {
                try {
                    int tempSerialNumber=i/3;
                    GPIO_VALUE value = GPIOAssist.getInstance().queryGPIOValue(usbDevice,tempSerialNumber , i);
                    gpiovalues |= (value.getValue()) << i;
                } catch (UartLibException e) {
                    LogUtil.d(e.getMessage());
                    return false;
                }
            }
        }
        gpiovalues |= gpioValue << gpioNumber;
        request = (byte) (VENDOR_WRITE_BIT_EXT_TYPE + portnum + PORT_OFFSET);
        regaddr = REG_GPIO_OUT;
        buffer[0] = request;
        buffer[1] = regaddr;
        buffer[2] = (byte) gpiovalues;
        buffer[3] = (byte) (gpiovalues >> 8);
        return ChipTransferUtil.cmdOut(connection,mCtrlPointOut,buffer)>=0;
    }

    private static Short getGPIOValueReal(UsbDevice usbDevice,UsbDeviceConnection connection, UsbEndpoint mCtrlPointOut
            ,int portnum,int GPIOCount,long timeout) throws UartLibException {
        byte[] buffer=new byte[4];
        int ret=0;
        byte request=0;
        byte regaddr=0;
        short gpiodirs = 0;
        int i;

        for (i = 0; i < GPIOCount; i++) {
            int tempSerialNumber=i/3;
            GPIO_DIR dir = GPIOAssist.getInstance().queryGPIODir(usbDevice, tempSerialNumber, i);
            gpiodirs |= (dir.getValue()) << i;
        }
        request = (byte) (VENDOR_WRITE_BIT_EXT_TYPE + portnum + PORT_OFFSET);
        regaddr = REG_GPIO_IN;
        buffer[0] = request;
        buffer[1] = regaddr;
        buffer[2] = (byte) gpiodirs;
        buffer[3] = (byte) (gpiodirs >> 8);
        if(ChipTransferUtil.cmdOut(connection,mCtrlPointOut,buffer)<0){
            return null;
        }
        int temp=0;
        Short gpioValLin=null;
        while (temp<timeout){
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
                break;
            }
            gpioValLin = GPIOAssist.getInstance().getGPIOValLin();
            if(gpioValLin!=null){
                return gpioValLin;
            }
            temp++;
        }
        return null;
    }

}
